package com.syl.springcloud.authorization.config;

import com.google.gson.Gson;
import com.google.gson.stream.JsonReader;
import com.syl.springcloud.authorization.bean.AuthorizationConfig;
import com.syl.springcloud.authorization.bean.ReturnMessage;
import com.syl.springcloud.authorization.bean.UserBean;
import com.syl.springcloud.authorization.filter.CustomAuthenticationFilter;
import com.syl.springcloud.authorization.filter.CustomPermissionFilter;
import com.syl.springcloud.authorization.service.CustomAuthenticationProvider;
import com.syl.springcloud.authorization.service.MyAccessDecisionManager;
import com.syl.springcloud.authorization.service.MyUserDetailsService;
import com.syl.springcloud.authorization.service.RestSessionExpiredStrategy;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.session.data.redis.RedisOperationsSessionRepository;
import org.springframework.session.security.SpringSessionBackedSessionRegistry;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.util.Set;

/**
 * @author syl
 * @create 2018-07-27 11:17
 **/
@Order(1)
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private MyUserDetailsService dbLoginService;
    @Autowired
    private AuthorizationConfig authorizationConfig;
    @Autowired
    private RedisTemplate<Object,Object> redisTemplate;

    /**
     * 设置不需要oauth2授权的地址
     * @return
     */
    private String[] getNoOAuth2Path(){ //获取不需要oauth2的地址
        Set<String> unimportance = authorizationConfig.getUnimportance();
        unimportance.add("/");
        unimportance.add("/home/**");
        unimportance.add("/login/**");
        unimportance.add("/logout/");//退出登录成功提示
        unimportance.add("/oauth/**");
        unimportance.add("/token/**");
        unimportance.add("/error/**");
        unimportance.add("/aes/**");
        return unimportance.toArray(new String[]{});
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.httpBasic().disable();
        http
        .exceptionHandling()
            .authenticationEntryPoint((request, response, authentication) ->
                returnJson(response,new ReturnMessage(401,authentication.getMessage()))
            )
            .accessDeniedHandler((request, response, authentication) ->
                returnJson(response,new ReturnMessage(403,authentication.getMessage()))
            )
        ;
        http
        .addFilterBefore(customPermissionFilter(),FilterSecurityInterceptor.class)
        .addFilterBefore(customAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
        .authenticationProvider(daoAuthProvider())
        .requestMatchers()
            .antMatchers(getNoOAuth2Path()) //接收以xxx请求的请求 (未被接受的皆受oauth2资源保护)
        .and()
        .formLogin()
        .and()
        .logout()
            .logoutSuccessUrl("/logout/")
        .and()
        .csrf()
            .disable()
        .sessionManagement()
            .invalidSessionStrategy(customSessionInvalid())
            .maximumSessions(1)
            .sessionRegistry(new SpringSessionBackedSessionRegistry<>(sessionRepository()))
//            .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
        ;
    }

    @Bean
    public RedisOperationsSessionRepository sessionRepository() {
        return new RedisOperationsSessionRepository(redisTemplate);
    }

    @Bean
    public CustomPermissionFilter customPermissionFilter() {
        CustomPermissionFilter customPermissionFilter = new CustomPermissionFilter();
        customPermissionFilter.setAccessDecisionManager(myAccessDecisionManager());
//        customPermissionFilter.setPublishAuthorizationSuccess(true); //授权失败打印日志
        return customPermissionFilter;
    }

    @Bean
    public RestSessionExpiredStrategy customSessionInvalid(){
        return new RestSessionExpiredStrategy();
    }

    @Bean
    public AccessDecisionManager myAccessDecisionManager() {
        return new MyAccessDecisionManager();
    }

    //注册自定义的UsernamePasswordAuthenticationFilter
    @Bean
    public CustomAuthenticationFilter customAuthenticationFilter() throws Exception {
        CustomAuthenticationFilter filter = new CustomAuthenticationFilter();
        filter.setAuthenticationSuccessHandler((request, response, authentication) -> {
            UserBean bean = getUserBean(request);
            System.out.println(bean);
            returnJson(response, new ReturnMessage(200, "登陆成功"));
        }
        );
        filter.setAuthenticationFailureHandler((request, response, authentication) ->
            returnJson(response, new ReturnMessage(400,authentication.getMessage()))
        );
//        filter.setFilterProcessesUrl("/login/self");

        //这句很关键，重用WebSecurityConfigurerAdapter配置的AuthenticationManager，不然要自己组装AuthenticationManager
        filter.setAuthenticationManager(authenticationManagerBean());
        return filter;
    }

    @Bean // share AuthenticationManager for web and oauth
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    /**
     * 配置数据库验证
     * @return
     */
    @Bean
    public CustomAuthenticationProvider daoAuthProvider() {
        CustomAuthenticationProvider provider = new CustomAuthenticationProvider();
        provider.setHideUserNotFoundExceptions(false);
        provider.setUserDetailsService(dbLoginService);
        provider.setPasswordEncoder(new BCryptPasswordEncoder());
        return provider;
    }

    private void returnJson(HttpServletResponse response, ReturnMessage msg) {
        response.setCharacterEncoding("UTF-8");
        response.setContentType("application/json");
        try {
            response.getWriter().println(msg.toJson());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private UserBean getUserBean(HttpServletRequest request){
        try {
            InputStream is = request.getInputStream();
            UserBean temp = new Gson().fromJson(new JsonReader(new InputStreamReader(is, "UTF-8")),UserBean.class);
            return temp;
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

}
